package nachos.threads;
////////////////////// CHECK LINE NUMBERS!!!! WILL PROBABLY BE WRONG SINCE I ADDED CODE
import nachos.machine.*;
import java.util.LinkedList; 

/**
  A KThread is a thread that can be used to execute Nachos kernel code. Nachos
  allows multiple threads to run concurrently.
 
  To create a new thread of execution, first declare a class that implements
  the Runnable interface. That class then implements the run()
  method. An instance of the class can then be allocated, passed as an
  argument when creating KThread, and forked. For example, a thread
  that computes pi could be written as follows:
 
 
  class PiRun implements Runnable 
  {
      public void run() 
	  {
          // compute pi
          ...
      }
  }
 
  //The following code would then create a thread and start it running:
 
  
  PiRun p = new PiRun();
  new KThread(p).fork();
 
 */
public class KThread 
{
    /**
      Get the current thread.
      Return the current thread.
    */
    public static KThread currentThread()									//Getter for the current thread								
	{
		Lib.assertTrue(currentThread != null);
		return currentThread;
    }
 
    /** 
	   Allocates a new KThread. If this is the first KThread, create an idle thread as well.
	*/
    public KThread() 														//Default constructor
	{													
		if (currentThread != null) 											//If there is another KThread running, do this:
		{
			tcb = new TCB();												//Create a new java thread using the TCB API
		}	    
		else 																//If this is the first KThread, do this:
		{																
			readyQueue = ThreadedKernel.scheduler.newThreadQueue(false);	//Creates the ready queue
			readyQueue.acquire(this);	    								//Allocate the CPU to the new KThread object being created 

			currentThread = this;											//Sets the static KThread, currentThread, to the thread being made
			tcb = TCB.currentTCB();											//Sets the currently running Java thread to the new KThread object being created
			name = "main";													//Sets the name of the thread to main (since it is the only thread)
			restoreState();													//Basically, sets the status to statusRunning... See line 373

			createIdleThread();												//Make another new KThread with an infinite yield() loop... See line 305
		}
    }

    /**
      Allocates a new KThread.
      Parameters: 
		target - the object whose run() method is called.
    */
    public KThread(Runnable target) 										//Takes a parameter that is Runnable... that has a run() function
	{									
		this();																//Calls default constructor
		this.target = target;												//Sets the target of the KThread to Runnable class passed as a parameter
    }

    /**
      Set the target of this thread.
      Parameters:
		target - the object whose run() method is called.
	  Returns:
		the KThread upon which setTarget was called
     */
    public KThread setTarget(Runnable target) 								//Sets the target of a KThread to a runnable class
	{
		Lib.assertTrue(status == statusNew);								//The KThread must be new
		this.target = target;												
		return this;
    }

    /**
      Set the name of this thread. This name is used for debugging purposes.
      Parameters:
		name - the name to give to this thread.
      Returns:	
		the KThread upon which setName was called
     */
    public KThread setName(String name) 									//Sets the name of a KThread, for debugging purposes
	{
		this.name = name;
		return this;
    }

    /**
      Get the name of this thread. This name is used for debugging purposes.
      Returns:
		KThread.name
     */     
    public String getName() 												//Returns KThread.name, for debugging purposes
	{												
		return name;
    }
	
    /**
      Get the full name of this thread. This includes its name along with its
      numerical ID. This name is used for debugging purposes.
      Returns:	KThread.name + " " + KThread.id
     */
    public String toString() 												//Returns KThread.name + " " + KThread.id, for debugging purposes
	{
		return (name + " (#" + id + ")");
    }

    /**
      Deterministically and consistently compare this thread to another thread.
     */
    public int compareTo(Object o) 											//Compares two threads
	{																		//Object is the root class in java, so all objects are of type Object
		KThread thread = (KThread) o;										//This casts the object to a KThread?

		if      (id < thread.id)											//Returns -1 if the object's id is greater
			{return -1;}
		else if (id > thread.id)											//Returns 1 if the object's id is lesser
			{return 1;}
		else																//Returns 0 if the ids are the same
			{return 0;}										
    }

    /**
      Causes this thread to begin execution. The result is that two threads
      are running concurrently: the current thread (which returns from the
      call to the fork() method) and the other thread (which executes
      its target's run() method).
     */
    public void fork() 														//Creates a new thread
	{
		Lib.assertTrue(status == statusNew);								//The KThread must be new
		Lib.assertTrue(target != null);										//The KThread must have a Runnable target
		Lib.debug(dbgThread, "Forking thread: " + toString() + " Runnable: " + target);	

		boolean intStatus = Machine.interrupt().disable();					//Disables interrupts and returns the old interrupt state

		tcb.start(new Runnable() {public void run() {runThread();}});		//Starts a child process that runs runThread()... See line 170

		ready();															//Moves the thread to the ready state and adds it to the ready queue
	
		Machine.interrupt().restore(intStatus);								//Restores interrupts to the old interrupt state
    }

	/**
      The following code would create a thread and fork it:
		PiRun p = new PiRun();
		new KThread(p).fork();												//Declares a new KThread right before forking it
	  
	  This calls the KThread constructor and assigns p to target
	  So target.run() is just like p.run()
     */
    private void runThread() 												//This is run whenever a new thread begins
	{												
		begin();															//Prepares the thread to be run... See line 179
		target.run();														//Calls the run() method of target...  See comment at line 163
		finish();															//Calls finish() once run() has completed... See line 198
    }

    private void begin() 													//Prepares a thread to be run
	{
		Lib.debug(dbgThread, "Beginning thread: " + toString());			//General Debugging
	
		Lib.assertTrue(this == currentThread);									

		restoreState();														//Sets thestate to running and destroys the previous thread if it is finished

		Machine.interrupt().enable();										//Enables interrupts
    }

    /**
      Finish the current thread and schedule it to be destroyed when it is
      safe to do so. This method is automatically called when a thread's
      run() method returns, but it may also be called directly.
     
      The current thread cannot be immediately destroyed because its stack and
      other execution state are still in use. Instead, this thread will be
      destroyed automatically by the next thread to run, when it is safe to
      delete this thread.
     */
    public static void finish() 											//Sets tobeDestroyed to the current thread so the next one can delete it
	{
		Lib.debug(dbgThread, "Finishing thread: " + currentThread.toString());
		Machine.interrupt().disable();										//Disable interrupts
		Machine.autoGrader().finishingCurrentThread();						//Some call to the autoGrader... Not important for us
		Lib.assertTrue(toBeDestroyed == null);

		toBeDestroyed = currentThread;										//Sets the current thread to be destroyed... The next thread will destroy it
		 
        boolean status = Machine.interrupt().disable();                 
		Machine.interrupt().restore(status);   
		
		currentThread.status = statusFinished;								//Sets the status of the current thread to finished
		sleep();															//Puts the current thread to sleep... It will never wake up... See line 250
	}

    /**
      Relinquish the CPU if any other thread is ready to run. If so, put the
      current thread on the ready queue, so that it will eventually be
      rescheuled.
     
      Returns immediately if no other thread is ready to run. Otherwise
      returns when the current thread is chosen to run again by
      readyQueue.nextThread().
     
      Interrupts are disabled, so that the current thread can atomically add
      itself to the ready queue and switch to the next thread. On return,
      restores interrupts to the previous state, in case yield() was
      called with interrupts disabled.
     */
    public static void yield() 
	{																		//Very similar structure to fork()
		Lib.debug(dbgThread, "Yielding thread: " + currentThread.toString());
		Lib.assertTrue(currentThread.status == statusRunning);
	
		boolean intStatus = Machine.interrupt().disable();					//Disables interrupts and returns the old interrupt state

		currentThread.ready();												//Moves the current thread from running to ready

		runNextThread();													//Runs another thread if possible, or runs the previous one... See line 321
		Machine.interrupt().restore(intStatus);								//Restores interrupts to the old interrupt state
    }

    /**
      Relinquish the CPU, because the current thread has either finished or it
      is blocked. This thread must be the current thread.
     
      If the current thread is blocked on a synchronization primitive(a Semaphore, Lock, or Condition),
	  eventually some thread will wake it up, putting it back on the ready queue
      so that it can be rescheduled. Otherwise, finish() should have
      scheduled this thread to be destroyed by the next thread to run.
     */
    public static void sleep() 												//Runs the next thread because the current one is finished or blocked
	{
		Lib.debug(dbgThread, "Sleeping thread: " + currentThread.toString());
		Lib.assertTrue(Machine.interrupt().disabled());

		if (currentThread.status != statusFinished)							//If the thread isn't finished, set its status to blocked
			currentThread.status = statusBlocked;

		runNextThread();													//Continue with the next thread
    }

    /**
      Moves this thread to the ready state and adds it to the scheduler's ready queue.
     */
    public void ready() 													//Sets the status to ready and puts the thread in the ready queue
	{
		Lib.debug(dbgThread, "Ready thread: " + toString());
		Lib.assertTrue(Machine.interrupt().disabled());
		Lib.assertTrue(status != statusReady);
	
		status = statusReady;												
		if (this != idleThread)												//If it isn't the idle thread, put it into the ready queue
			readyQueue.waitForAccess(this);
	
		Machine.autoGrader().readyThread(this);								//Some call to the autoGrader, can be ignored
    }

    /**
      Waits for this thread to finish. If this thread is already finished,
      return immediately. This method must only be called once; the second
      call is not guaranteed to return. This thread must not be the current
      thread.
     */
    public void join() 		//Current thread calls target.join() ... waits until target finishes
	{
		Lib.debug(dbgThread, "Joining to thread: " + toString());	//debugging statements
		Lib.assertTrue(this != currentThread);						//debugging statements
		
		if (this.status == statusFinished)		//if the target is already finished, do nothing
			return;
             	
		boolean status = Machine.interrupt().disable();     
		while(this.status != statusFinished)	//When caller gets the CPU, check if the target is finished...
			currentThread.yield();				//If target isn't finished, caller puts itself in the back
		Machine.interrupt().restore(status);    //of the ready queue
	}        			
	
    /**
      Create the idle thread. Whenever there are no threads ready to be run
      and runNextThread() is called, it will run the idle thread. The
      idle thread must never block, and it will only be allowed to run when
      all other threads are blocked.
     
      Note that ready() never adds the idle thread to the ready set.
     */
    private static void createIdleThread() 									//Called by default constructor, creates the idle thread
	{
		Lib.assertTrue(idleThread == null);
	
		idleThread = new KThread(new Runnable() {public void run() { while (true) yield(); }});	//Creates the idle thread which always yields
		
		idleThread.setName("idle");

		Machine.autoGrader().setIdleThread(idleThread);						//Some call to the autoGrader, can be ignored
	
		idleThread.fork();													//Starts the idle thread and adds it to the ready queue
    }
    
    /**
      Determine the next thread to run, then dispatch the CPU to the thread using run().
     */
    private static void runNextThread() 									//Runs the next available thread, or runs the idle thread
	{
		KThread nextThread = readyQueue.nextThread();
		if (nextThread == null)
			nextThread = idleThread;

		nextThread.run();
    }

    /**
      Dispatch the CPU to this thread. Save the state of the current thread,
      switch to the new thread by calling TCB.contextSwitch(), and
      load the state of the new thread. The new thread becomes the current
      thread.
     
      If the new thread and the old thread are the same, this method must
      still call saveState(), contextSwitch(), and
      restoreState().
     
      The state of the previously running thread must already have been
      changed from running to blocked or ready (depending on whether the
      thread is sleeping or yielding).
     
      Parameters:
		finishing - true if the current thread is finished,
     				and should be destroyed by the new thread.
				  - false otherwise
     */
    private void run() 														//Transfers contol to the specified thread
	{
		Lib.assertTrue(Machine.interrupt().disabled());
		
		if(this.status != statusBlocked)
		{
			Machine.yield();												//Unsure what this does

			currentThread.saveState();										//Saves the state of the current thread... See line 394
	
			Lib.debug(dbgThread, "Switching from: " + currentThread.toString() + " to: " + toString());

			currentThread = this;											//Makes this thread the current thread

			tcb.contextSwitch();											//Switches from the current TCB to tcb

			currentThread.restoreState();									//Loads in the state of the new current thread
		}
    }

    /**
      Prepare this thread to be run. Set status to
      statusRunning and check toBeDestroyed.
     */
    protected void restoreState() 											//Sets the thread to running and possibly destroys any finished threads
	{
		Lib.debug(dbgThread, "Running thread: " + currentThread.toString());
		Lib.assertTrue(Machine.interrupt().disabled());
		Lib.assertTrue(this == currentThread);
		Lib.assertTrue(tcb == TCB.currentTCB());
		Machine.autoGrader().runningThread(this);
	
		status = statusRunning;												//Sets the state to running

		if (toBeDestroyed != null) 											//Destroys the previous thread if it is finished
		{
			toBeDestroyed.tcb.destroy();
			toBeDestroyed.tcb = null;
			toBeDestroyed = null;
		}
    }

    /**
      Prepare this thread to give up the processor. Kernel threads do not need to do anything here.
     */
    protected void saveState() 												//Saves the state of the thread so it can give up the CPU
	{																		
		Lib.assertTrue(Machine.interrupt().disabled());
		Lib.assertTrue(this == currentThread);
    }

    private static class PingTest implements Runnable 						//Called in selfTest, line 422
	{
		PingTest(int which) 												//Constructor for PingTest, takes an int as an identifier
		{
			this.which = which;												//The private which (declared line 415) gets the value of the parameter
		}
	
		public void run() 
		{
			for (int i=0; i<5; i++) 
			{
				System.out.println("*** thread " + which + " looped " + i + " times");
				currentThread.yield();
			}
		}
		
		private int which;
    }

    /**
      Tests whether this module is working.
     */
    public static void selfTest() 											//This is the method that writes all that code when you run nachos!
	{
		Lib.debug(dbgThread, "Enter KThread.selfTest");
	
//		new KThread(new PingTest(1)).setName("forked thread").fork();		//Declares a new KThread by passing a PingTest object to it that is already forked
//		new PingTest(2).run();												//Runs a second PingTest... See line 400
    }

    private static final char dbgThread = 't';

    /**
     * Additional state used by schedulers.
     *
     * @see	nachos.threads.PriorityScheduler.ThreadState
     */
    public Object schedulingState = null;

    public static final int statusNew = 0;
    public static final int statusReady = 1;
    public static final int statusRunning = 2;
    public static final int statusBlocked = 3;
    public static final int statusFinished = 4;

    /**
      The status of this thread. A thread can either be new (not yet forked),
      ready (on the ready queue but not running), running, or blocked (not
      on the ready queue and not running).
     */
    public int status = statusNew;
    private String name = "(unnamed thread)";
    private Runnable target;												//The keyword Runnable means that it has a run() method and can be a thread
    private TCB tcb;														//Stores the thread that calls join on this KThread
	
    /** Unique identifer for this thread. Used to deterministically compare threads. */
    private int id = numCreated++;
    /** Number of times the KThread constructor was called. */
    private static int numCreated = 0;										//Not incremented in the constructor... Where does it come from? See line 455

    private static ThreadQueue readyQueue = null;
    private static KThread currentThread = null;
    private static KThread toBeDestroyed = null;
    private static KThread idleThread = null;
}